#include "pThreads/pthread.h"
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"

#include <windows.h>
#include <time.h>
#include <sys/timeb.h>
#include "CConsole.h"

CSemaphore::CSemaphore(void)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,1); //Create a semaphore with count 1 (mutex)
#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_keepLogOpen = false;
	m_isDebuggingEnabled = false;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, 1);
#endif
}

CSemaphore::CSemaphore(int count)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,count); //Create a semaphore
#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_keepLogOpen = false;
	m_isDebuggingEnabled = false;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, count);
#endif
}

CSemaphore::CSemaphore(bool debuggingEnabled)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,1); //Create a semaphore with count 1 (mutex)

#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_keepLogOpen = false;
	m_isDebuggingEnabled = debuggingEnabled;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, 1);
#endif
}

CSemaphore::CSemaphore(bool debuggingEnabled, bool keepLogOpen)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,1); //Create a semaphore with count 1 (mutex)

#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_isDebuggingEnabled = debuggingEnabled;
	m_keepLogOpen = keepLogOpen;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, 1);
#endif
}

CSemaphore::CSemaphore(int count, bool debuggingEnabled)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,count); //Create a semaphore
#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_keepLogOpen = false;
	m_isDebuggingEnabled = debuggingEnabled;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, count);
#endif
}

CSemaphore::CSemaphore(int count, bool debuggingEnabled, bool keepLogOpen)
{
	sem_init(&m_ThreadInfoMutex,0,1);
	sem_init(&m_semaphore,0,count); //Create a semaphore
#ifdef __SEMAPHORE_DEBUG_
	sem_init(&m_LogMutex,0,1);
	logfile = NULL;
	m_keepLogOpen = keepLogOpen;
	m_isDebuggingEnabled = debuggingEnabled;
	logDebugMessage("Semaphore %.8X created. Count: %d!", this, count);
#endif
}

CSemaphore::~CSemaphore(void)
{
	#ifdef __SEMAPHORE_DEBUG_
	logDebugMessage("Semaphore %.8X destroyed!", this);
	sem_destroy(&m_LogMutex);
	#endif
	sem_destroy(&m_ThreadInfoMutex);
	sem_destroy(&m_semaphore);
}

/*
This function tries to lock the semaphore
Return Value:
	True if the thread locked the semaphore before ( with tryLock(), timedLock() or lock() )
	True if the semaphore can be locked
	False else

	If the return value is true the thread will be added to the list of threads that passed that semaphore
	If the thread is already in that list, it's passed counter will be increased by 1
*/
bool CSemaphore::tryLock(void)
{
	sem_wait(&m_ThreadInfoMutex);
	for (unsigned int entry=0;entry<m_threadInfo.size();entry++)
	{
		//check if the current thread already locked the semaphore
		if (m_threadInfo.at(entry).threadId==GetCurrentThreadId())
		{
			m_threadInfo.at(entry).nrPassed++;
			sem_post(&m_ThreadInfoMutex);
			return true; //no need to try again
		}
	}
	sem_post(&m_ThreadInfoMutex);

	bool retVal=forceTryLock();
	if (retVal)
	{
		//add thread to the list
		CSemThreadInfo newInfo;
		newInfo.threadId=GetCurrentThreadId();
		newInfo.nrPassed=0;

		sem_wait(&m_ThreadInfoMutex);
		m_threadInfo.push_back(newInfo);
		sem_post(&m_ThreadInfoMutex);
	}
	return retVal;
}

/*
This function locks the semaphore
It will return without touching the semaphore, if the calling thread locked the semaphore before ( with tryLock(), timedLock() or lock() )
Else it will try to lock the semaphore, after it succeeds it will add the Thread to the list of threads.
*/
bool CSemaphore::lock(void)
{
	return lock(true);
}

bool CSemaphore::lock(bool debuggingEnabled)
{
	DWORD currThreadId = GetCurrentThreadId(); 
	sem_wait(&m_ThreadInfoMutex);
	for (unsigned int entry=0;entry<m_threadInfo.size();entry++)
	{
		//check if the current thread already locked the semaphore
		if (m_threadInfo.at(entry).threadId==currThreadId)
		{
			m_threadInfo.at(entry).nrPassed++;
			sem_post(&m_ThreadInfoMutex);
			return true; //no need to lock the semaphore
		}
	}
	sem_post(&m_ThreadInfoMutex);

	bool retVal = forceLock(debuggingEnabled);

	//add thread to the list
	CSemThreadInfo newInfo;
	newInfo.threadId=GetCurrentThreadId();
	newInfo.nrPassed=0;

	sem_wait(&m_ThreadInfoMutex);
	m_threadInfo.push_back(newInfo);
	sem_post(&m_ThreadInfoMutex);

	return retVal;
}

/*
This function tries to lock the semaphore, if the semaphore can't be locked in milliSeconds it will stop trying.
Return values:
	True if the thread locked the semaphore before ( with tryLock(), timedLock() or lock() )
	True if the semaphore can be locked in less than milliSeconds
	False else

If the return value is true the thread will be added to the list of threads that passed that semaphore
If the thread is already in that list, it's passed counter will be increased by 1
*/
bool CSemaphore::timedLock(int waitMilliSecs)
{
	sem_wait(&m_ThreadInfoMutex);
	for (unsigned int entry=0;entry<m_threadInfo.size();entry++)
	{
		//check if the current thread already locked the semaphore
		if (m_threadInfo.at(entry).threadId==GetCurrentThreadId())
		{
			m_threadInfo.at(entry).nrPassed++;
			return true; //no need to lock the semaphore
		}
	}
	sem_post(&m_ThreadInfoMutex);

	bool retVal=forceTimedLock(waitMilliSecs);

	if (retVal)
	{
		//add thread to the list
		CSemThreadInfo newInfo;
		newInfo.threadId=GetCurrentThreadId();
		newInfo.nrPassed=0;

		sem_wait(&m_ThreadInfoMutex);
		m_threadInfo.push_back(newInfo);
		sem_post(&m_ThreadInfoMutex);
	}
	return retVal;
}

/*
This function unlock the semaphore
If the calling thread is in the list of threads that passed the semaphore this function will check how many times this thread
passed the semaphore without locking.
While this value is >0 the semaphore won't be unlocked.
*/
bool CSemaphore::unlock(void)
{
	return unlock(true);
}

bool CSemaphore::unlock(bool debuggingEnabled)
{
	sem_wait(&m_ThreadInfoMutex);
	for (unsigned int entry=0;entry<m_threadInfo.size();entry++)
	{
		//check if the current thread passed the semaphore more than once
		//without locking it
		if (m_threadInfo.at(entry).threadId==GetCurrentThreadId())
		{
			if (m_threadInfo.at(entry).nrPassed>0)
			{
				m_threadInfo.at(entry).nrPassed--;
				sem_post(&m_ThreadInfoMutex);
				return true; //no need to unlock the semaphore
			}
			else //nrPassed==0 => we need to remove the thread from the list and unlock the semaphore
			{
				m_threadInfo.erase(m_threadInfo.begin()+entry,m_threadInfo.begin()+entry+1);
				break;
			}
		}
	}
	sem_post(&m_ThreadInfoMutex);

	//If this point is reached the thread either
	//a) was not in the list or
	//b) was in the list, but with nrPassed==0
	//in both cases we need to unlock the semaphore
	return forceUnlock(debuggingEnabled);
	 
}

/*
This functions tries to lock the semaphore. This function doesn't add the thread to the list of threads that locked this semaphore.
Return values:
	True if the semaphore can be locked
	False else
*/
bool CSemaphore::forceTryLock(void)
{
	int retVal=sem_trywait(&m_semaphore);

	if (retVal==0)
		return true;

	return false;
}

/*
This function locks the semaphore without adding the thread to the list.
*/
bool CSemaphore::forceLock(void)
{
	return this->forceLock(true);
}

bool CSemaphore::forceLock(bool enableDeadlockDetection)
{
	int retVal;
#ifndef __SEMAPHORE_DEBUG_
	retVal=sem_wait(&m_semaphore);
	return (retVal==0);
#else
	logDebugMessage("Thread %.8X tries to lock semaphore %.8X!", GetCurrentThreadId(),this);
	if (enableDeadlockDetection && m_isDebuggingEnabled)
	{
		//waittime
		struct __timeb64 currSysTime;
		struct timespec waittime={0,0};
		retVal=-1;
		while(retVal!=0)
		{
			_ftime(&currSysTime);
			waittime.tv_sec=(long)(currSysTime.time+3);
			
			retVal=sem_timedwait(&m_semaphore,&waittime);

			if (retVal!=0)
			{
				//For semaphore debugging you should set a breakpoint here
				logDebugMessage("Thread %.8X fails to lock Semaphore %.8X! Semaphore error: %d.\n", GetCurrentThreadId(),this ,retVal);
				printf("nop");
				printf("nop");
			}
		}
		if (retVal == 0)
			logDebugMessage("Thread %.8X locked semaphore %.8X!", GetCurrentThreadId(),this);
		else 
			logDebugMessage("Thread %.8X failed to locked semaphore %.8X!", GetCurrentThreadId(),this);
	}
	else
		retVal=sem_wait(&m_semaphore);
	return (retVal==0);
#endif 
}

#ifdef __SEMAPHORE_DEBUG_
void CSemaphore::logDebugMessage(char* buf ,... )
{
	if (!m_isDebuggingEnabled)
		return;

	va_list ap;
	char text[1024];
	char text2[1024];

	va_start(ap, buf);		// Parses The String For Variables
		vsprintf(text, buf, ap);	// And Converts Symbols To Actual Numbers
	va_end(ap);

	struct tm * timeinfo;
	char timestamp[100];
	time_t rawtime;

	time(&rawtime);
	timeinfo = localtime(&rawtime);
	strftime(timestamp, 100, "[%m/%d/%y %H:%M:%S]", timeinfo);

	sprintf(text2,"%s%s\n",timestamp,text);

	strcpy(text,text2);

	sem_wait(&m_LogMutex);

	if (logfile == NULL)
	{
		char filename[256];
		sprintf(filename,"semdebug/sem_ops_%.8X.log",this);
		logfile = fopen(filename,"a+");
	}

	if (logfile != NULL)
		fwrite(text,1,strlen(text),logfile);

	fflush(logfile);
	if (!m_keepLogOpen)
	{
		fclose(logfile);
		logfile = NULL;
	}

	sem_post(&m_LogMutex);
}
#endif

/*
This function unlocks the semaphore. It doesn't check if the thread locked the semaphore before.
*/
bool CSemaphore::forceUnlock(void)
{
	return forceUnlock(true);
}

bool CSemaphore::forceUnlock(bool debuggingEnabled)
{
	#ifdef __SEMAPHORE_DEBUG_
	if (debuggingEnabled)
		logDebugMessage("Thread %.8X tries to unlock semaphore %.8X!", GetCurrentThreadId(),this);
	#endif

	int retVal=sem_post(&m_semaphore);

	#ifdef __SEMAPHORE_DEBUG_
	if (debuggingEnabled)
	{
		if (retVal == 0)
			logDebugMessage("Thread %.8X unlocked semaphore %.8X!", GetCurrentThreadId(),this);
		else 
			logDebugMessage("Thread %.8X failed to unlocked semaphore %.8X!", GetCurrentThreadId(),this);
	}
	#endif 
	return (retVal==0);
}

/*
This function tries to lock the semaphore within milliSeconds. It doesn't check if the thread locked the semaphore before.
Return values:
	True if the semaphore can be locked in less than milliSeconds.
	False else
*/
bool CSemaphore::forceTimedLock(int waitMilliSecs)
{
	//waittime
	struct __timeb64 currSysTime;
	struct timespec waittime={0,0};

	int retVal=-1;

	_ftime(&currSysTime);
	long seconds = (long) (waitMilliSecs/1000);
	long nanoSeconds = (waitMilliSecs%1000)*1000000;

	waittime.tv_sec=(long)(currSysTime.time+seconds);
	waittime.tv_nsec=nanoSeconds;
	
	retVal=sem_timedwait(&m_semaphore,&waittime);

	if (retVal==0)
		return true;

	return false;
}

//Debug functions

/*
This functions returns the current semaphore counter c.
If c>0 then the semaphore can be locked c times
if c=0 then the semaphore can not be locked atm.
if c<0 then the semaphore can not be locked atm and there are currently c threads trying to lock the semaphore (with sem_wait or sem_timedwait)

This function does not work unless __SEMAPHORE_DEBUG_ is defined. 
Accessing the semaphore counter is not safe, i only added this function for debugging and you shouldn't need it for your code.
Under certain circumstances the c returned by this function can be false (e.g. if another thread tries to lock/unlock the semaphore at the same time)
*/
int CSemaphore::getSemaphoreCounter(void)
{
	int retVal=0;

	#ifdef __SEMAPHORE_DEBUG_
	sem_getvalue(&m_semaphore,&retVal);
	#endif

	return retVal;
}
